package com.xiaopo.flying.puzzle;

import com.xiaopo.flying.puzzle.ext.PointF;
import com.xiaopo.flying.puzzle.ext.RectF;
import com.xiaopo.flying.puzzle.util.AttrValue;
import ohos.agp.components.AttrSet;
import ohos.agp.components.Component;
import ohos.agp.components.Image;
import ohos.agp.components.element.Element;
import ohos.agp.components.element.PixelMapElement;
import ohos.agp.render.Canvas;
import ohos.agp.render.Paint;
import ohos.agp.render.PixelMapHolder;
import ohos.agp.render.Texture;
import ohos.agp.utils.Color;
import ohos.agp.utils.Matrix;
import ohos.agp.utils.RectFloat;
import ohos.app.Context;
import ohos.app.dispatcher.TaskDispatcher;
import ohos.app.dispatcher.task.Revocable;
import ohos.media.image.PixelMap;
import ohos.media.image.common.PixelFormat;
import ohos.media.image.common.Size;
import ohos.multimodalinput.event.TouchEvent;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;

/**
 * @author wupanjie
 */
@SuppressWarnings({"unused", "SameParameterValue"})
public class PuzzleView extends Component implements Component.DrawTask, Component.TouchEventListener {
    private static final String TAG = "PuzzleView";
    private Revocable revocable;

    private enum ActionMode {
        NONE, DRAG, ZOOM, MOVE, SWAP
    }

    private ActionMode currentMode = ActionMode.NONE;

    private List<PuzzlePiece> puzzlePieces = new ArrayList<>();
    private List<PuzzlePiece> needChangePieces = new ArrayList<>();
    private Map<Area, PuzzlePiece> areaPieceMap = new HashMap<>();

    private PuzzleLayout puzzleLayout;
    private PuzzleLayout.Info initialInfo;

    private RectF bounds;
    private int lineSize;

    private int duration;
    private Line handlingLine;

    private PuzzlePiece handlingPiece;
    private PuzzlePiece replacePiece;
    private PuzzlePiece previousHandlingPiece;

    private Paint linePaint;
    private Paint selectedAreaPaint;
    private Paint handleBarPaint;

    private float downX;
    private float downY;
    private float previousDistance;
    private PointF midPoint;
    private boolean needDrawLine;

    private boolean needDrawOuterLine;
    private boolean touchEnable = true;
    private int lineColor;

    private int selectedLineColor;
    private int handleBarColor;
    private float piecePadding;
    private float pieceRadian;

    private boolean needResetPieceMatrix = true;
    private boolean quickMode = false;

    private boolean canDrag = true;
    private boolean canMoveLine = true;
    private boolean canZoom = true;
    private boolean canSwap = true;

    private OnPieceSelectedListener onPieceSelectedListener;
    private TaskDispatcher taskDispatcher = mContext.getUITaskDispatcher();
    private boolean isEstimateSize = false;

    public PuzzleView(Context context) {
        this(context, null);
    }

    public PuzzleView(Context context, AttrSet attrs) {
        this(context, attrs, 0);
    }

    public PuzzleView(Context context, AttrSet attrs, int defStyleAttr) {
        super(context, attrs, defStyleAttr);
        init(context, attrs);
    }

    private void init(Context context, AttrSet attrSet) {
        lineSize = AttrValue.get(attrSet, "line_size", 4);
        lineColor = AttrValue.get(attrSet, "line_color", Color.WHITE.getValue());
        selectedLineColor = AttrValue.get(attrSet, "selected_line_color", Color.getIntColor("#99BBFB"));
        handleBarColor = AttrValue.get(attrSet, "handle_bar_color", Color.getIntColor("#99BBFB"));
        piecePadding = AttrValue.get(attrSet, "piece_padding", 0);
        needDrawLine = AttrValue.get(attrSet, "need_draw_line", false);
        needDrawOuterLine = AttrValue.get(attrSet, "need_draw_outer_line", false);
        duration = AttrValue.get(attrSet, "animation_duration", 300);
        pieceRadian = AttrValue.get(attrSet, "radian", 0f);

        bounds = new RectF();

        // init some paint
        linePaint = new Paint();
        linePaint.setAntiAlias(true);
        linePaint.setColor(Color.WHITE);
        linePaint.setStrokeWidth(lineSize);
        linePaint.setStyle(Paint.Style.STROKE_STYLE);
        linePaint.setStrokeJoin(Paint.Join.ROUND_JOIN);
        linePaint.setStrokeCap(Paint.StrokeCap.SQUARE_CAP);

        selectedAreaPaint = new Paint();
        selectedAreaPaint.setAntiAlias(true);
        selectedAreaPaint.setStyle(Paint.Style.STROKE_STYLE);
        selectedAreaPaint.setStrokeJoin(Paint.Join.ROUND_JOIN);
        selectedAreaPaint.setStrokeCap(Paint.StrokeCap.ROUND_CAP);
        selectedAreaPaint.setColor(new Color(selectedLineColor));
        selectedAreaPaint.setStrokeWidth(lineSize);

        handleBarPaint = new Paint();
        handleBarPaint.setAntiAlias(true);
        handleBarPaint.setStyle(Paint.Style.FILL_STYLE);
        handleBarPaint.setColor(new Color(handleBarColor));
        handleBarPaint.setStrokeWidth(lineSize * 3);
        midPoint = new PointF();
        setTouchEventListener(this);
        addDrawTask(this);
    }

    public void onEstimateSize() {
        resetPuzzleBounds();
        areaPieceMap.clear();
        if (puzzlePieces.size() != 0) {
            for (int ii = 0; ii < puzzlePieces.size(); ii++) {
                PuzzlePiece piece = puzzlePieces.get(ii);
                Area area = puzzleLayout.getArea(ii);
                piece.setArea(area);
                areaPieceMap.put(area, piece);
                if (needResetPieceMatrix) {
                    piece.set(MatrixUtils.generateMatrix(piece, 0f));
                } else {
                    piece.fillArea(this, true);
                }
            }
        }
        invalidate();
    }

    private void resetPuzzleBounds() {
        bounds.left = getPaddingLeft();
        bounds.top = getPaddingTop();
        bounds.right = getWidth() - getPaddingRight();
        bounds.bottom = getHeight() - getPaddingBottom();

        if (puzzleLayout != null) {
            puzzleLayout.reset();
            puzzleLayout.setOuterBounds(bounds);
            puzzleLayout.layout();
            puzzleLayout.setPadding(piecePadding);
            puzzleLayout.setRadian(pieceRadian);

            if (initialInfo != null) {
                final int size = initialInfo.lineInfos.size();
                for (int i = 0; i < size; i++) {
                    PuzzleLayout.LineInfo lineInfo = initialInfo.lineInfos.get(i);
                    Line line = puzzleLayout.getLines().get(i);
                    line.startPoint().setPointX(lineInfo.startX);
                    line.startPoint().setPointY(lineInfo.startY);
                    line.endPoint().setPointX(lineInfo.endX);
                    line.endPoint().setPointY(lineInfo.endY);
                }
            }

            puzzleLayout.sortAreas();
            puzzleLayout.update();
        }
    }

    @Override
    public void onDraw(Component component, Canvas canvas) {
        if (puzzleLayout == null) {
            return;
        }
        if (!isEstimateSize) {
            onEstimateSize();
            isEstimateSize = true;
        }
        linePaint.setStrokeWidth(lineSize);
        selectedAreaPaint.setStrokeWidth(lineSize);
        handleBarPaint.setStrokeWidth(lineSize * 3);

        // draw pieces
        for (int i = 0; i < puzzleLayout.getAreaCount(); i++) {
            if (i >= puzzlePieces.size()) {
                break;
            }

            PuzzlePiece piece = puzzlePieces.get(i);

            if (piece == handlingPiece && currentMode == ActionMode.SWAP) {
                continue;
            }

            if (puzzlePieces.size() > i) {
                piece.draw(canvas, quickMode);
            }
        }

        // draw outer bounds
        if (needDrawOuterLine) {
            for (Line outerLine : puzzleLayout.getOuterLines()) {
                drawLine(canvas, outerLine);
            }
        }

        // draw slant lines
        if (needDrawLine) {
            for (Line line : puzzleLayout.getLines()) {
                drawLine(canvas, line);
            }
        }

        // draw selected area
        if (handlingPiece != null && currentMode != ActionMode.SWAP) {
            drawSelectedArea(canvas, handlingPiece);
        }

        // draw swap piece
        if (handlingPiece != null && currentMode == ActionMode.SWAP) {
            handlingPiece.draw(canvas, 128, quickMode);
            if (replacePiece != null) {
                drawSelectedArea(canvas, replacePiece);
            }
        }
    }

    private void drawSelectedArea(Canvas canvas, PuzzlePiece piece) {
        final Area area = piece.getArea();
        // draw select area
        canvas.drawPath(area.getAreaPath(), selectedAreaPaint);

        // draw handle bar-
        for (Line line : area.getLines()) {
            if (puzzleLayout.getLines().contains(line)) {
                PointF[] handleBarPoints = area.getHandleBarPoints(line);
                canvas.drawLine(handleBarPoints[0].getPointX(), handleBarPoints[0].getPointY(), handleBarPoints[1].getPointX(),
                    handleBarPoints[1].getPointY(), handleBarPaint);
                canvas.drawCircle(handleBarPoints[0].getPointX(), handleBarPoints[0].getPointY(), lineSize * 3f / 2,
                    handleBarPaint);
                canvas.drawCircle(handleBarPoints[1].getPointX(), handleBarPoints[1].getPointY(), lineSize * 3f / 2,
                    handleBarPaint);
            }
        }
    }

    private void drawLine(Canvas canvas, Line line) {
        canvas.drawLine(line.startPoint().getPointX(), line.startPoint().getPointY(), line.endPoint().getPointX(), line.endPoint().getPointY(),
            linePaint);
    }

    public void setPuzzleLayout(PuzzleLayout puzzleLayout) {
        clearPieces();

        this.puzzleLayout = puzzleLayout;

        puzzleLayout.setOuterBounds(bounds);
        puzzleLayout.layout();

        invalidate();
    }

    public void setPuzzleLayout(PuzzleLayout.Info info) {
        this.initialInfo = info;
        clearPieces();

        this.puzzleLayout = PuzzleLayoutParser.parse(info);

        this.piecePadding = info.padding;
        this.pieceRadian = info.radian;
        invalidate();
    }

    public PuzzleLayout getPuzzleLayout() {
        return puzzleLayout;
    }

    private float getTouchX(TouchEvent touchEvent, int index) {
        float getX = 0;
        if (touchEvent.getPointerCount() > index) {
            int[] xy = getLocationOnScreen();
            if (xy != null && xy.length == 2) {
                getX = touchEvent.getPointerScreenPosition(index).getX() - xy[0];
            } else {
                getX = touchEvent.getPointerPosition(index).getX();
            }
        }
        return getX;
    }

    private float getTouchY(TouchEvent touchEvent, int index) {
        float getY = 0;
        if (touchEvent.getPointerCount() > index) {
            int[] xy = getLocationOnScreen();
            if (xy != null && xy.length == 2) {
                getY = touchEvent.getPointerScreenPosition(index).getY() - xy[1];
            } else {
                getY = touchEvent.getPointerPosition(index).getY();
            }
        }
        return getY;
    }

    @Override
    public boolean onTouchEvent(Component component, TouchEvent event) {
        if (!touchEnable) {
            return false;
        }
        switch (event.getAction()) {
            case TouchEvent.PRIMARY_POINT_DOWN:
                downX = getTouchX(event, 0);
                downY = getTouchY(event, 0);

                decideActionMode(event);
                prepareAction(event);
                break;

            case TouchEvent.OTHER_POINT_DOWN:
                previousDistance = calculateDistance(event);
                calculateMidPoint(event, midPoint);
                decideActionMode(event);
                break;

            case TouchEvent.POINT_MOVE:
                performAction(event);

                if ((Math.abs(getTouchX(event, 0) - downX) > 10 || Math.abs(getTouchY(event, 0) - downY) > 10)
                    && currentMode != ActionMode.SWAP) {
                    if (revocable != null) {
                        revocable.revoke();
                    }
                }

                break;

            case TouchEvent.CANCEL:
            case TouchEvent.PRIMARY_POINT_UP:
                finishAction(event);
                currentMode = ActionMode.NONE;
                if (revocable != null) {
                    revocable.revoke();
                }
                break;
        }

        invalidate();
        return true;
    }

    // 决定应该执行什么Action
    private void decideActionMode(TouchEvent event) {
        for (PuzzlePiece piece : puzzlePieces) {
            if (piece.isAnimateRunning()) {
                currentMode = ActionMode.NONE;
                return;
            }
        }

        if (event.getPointerCount() == 1) {
            handlingLine = findHandlingLine();
            if (handlingLine != null && canMoveLine) {
                currentMode = ActionMode.MOVE;
            } else {
                handlingPiece = findHandlingPiece();

                if (handlingPiece != null && canDrag) {
                    currentMode = ActionMode.DRAG;
                    revocable = taskDispatcher.delayDispatch(new Runnable() {
                        @Override
                        public void run() {
                            if (!canSwap) return;
                            currentMode = ActionMode.SWAP;
                            invalidate();
                        }
                    }, 500);
                }
            }
        } else if (event.getPointerCount() > 1) {
            if (handlingPiece != null
                && handlingPiece.contains(getTouchX(event, 1), getTouchY(event, 1))
                && currentMode == ActionMode.DRAG
                && canZoom) {
                currentMode = ActionMode.ZOOM;
            }
        }
    }

    // 执行Action前的准备工作
    @SuppressWarnings("unused")
    private void prepareAction(TouchEvent event) {
        switch (currentMode) {
            case NONE:
                break;
            case DRAG:
                handlingPiece.record();
                break;
            case ZOOM:
                handlingPiece.record();
                break;
            case MOVE:
                handlingLine.prepareMove();
                needChangePieces.clear();
                needChangePieces.addAll(findNeedChangedPieces());
                for (PuzzlePiece piece : needChangePieces) {
                    piece.record();
                    piece.setPreviousMoveX(downX);
                    piece.setPreviousMoveY(downY);
                }
                break;
        }
    }

    // 执行Action
    private void performAction(TouchEvent event) {
        switch (currentMode) {
            case NONE:
                break;
            case DRAG:
                dragPiece(handlingPiece, event);
                break;
            case ZOOM:
                zoomPiece(handlingPiece, event);
                break;
            case SWAP:
                dragPiece(handlingPiece, event);
                replacePiece = findReplacePiece(event);
                break;
            case MOVE:
                moveLine(handlingLine, event);
                break;
        }
    }

    // 结束Action
    private void finishAction(TouchEvent event) {
        switch (currentMode) {
            case NONE:
                break;
            case DRAG:
                if (handlingPiece != null && !handlingPiece.isFilledArea()) {
                    handlingPiece.moveToFillArea(this);
                }

                if (previousHandlingPiece == handlingPiece
                    && Math.abs(downX - getTouchX(event, 0)) < 3
                    && Math.abs(downY - getTouchY(event, 0)) < 3) {
                    handlingPiece = null;
                }

                previousHandlingPiece = handlingPiece;
                break;
            case ZOOM:
                if (handlingPiece != null && !handlingPiece.isFilledArea()) {
                    if (handlingPiece.canFilledArea()) {
                        handlingPiece.moveToFillArea(this);
                    } else {
                        handlingPiece.fillArea(this, false);
                    }
                }
                previousHandlingPiece = handlingPiece;
                break;
            case MOVE:
                break;
            case SWAP:
                if (handlingPiece != null && replacePiece != null) {
                    swapPiece();

                    handlingPiece = null;
                    replacePiece = null;
                    previousHandlingPiece = null;
                }
                break;
        }

        // trigger listener
        if (handlingPiece != null && onPieceSelectedListener != null) {
            onPieceSelectedListener.onPieceSelected(handlingPiece,
                puzzlePieces.indexOf(handlingPiece));
        }

        handlingLine = null;
        needChangePieces.clear();
    }

    private void swapPiece() {
        Element temp = handlingPiece.getDrawable();
        String tempPath = handlingPiece.getPath();

        handlingPiece.setDrawable(replacePiece.getDrawable());
        handlingPiece.setPath(replacePiece.getPath());
        replacePiece.setDrawable(temp);
        replacePiece.setPath(tempPath);

        handlingPiece.fillArea(this, true);
        replacePiece.fillArea(this, true);
    }

    private void moveLine(Line line, TouchEvent event) {
        if (line == null || event == null) return;

        boolean needUpdate;
        if (line.direction() == Line.Direction.HORIZONTAL) {
            needUpdate = line.move(getTouchY(event, 0) - downY, 80);
        } else {
            needUpdate = line.move(getTouchX(event, 0) - downX, 80);
        }

        if (needUpdate) {
            puzzleLayout.update();
            puzzleLayout.sortAreas();
            updatePiecesInArea(line, event);
        }
    }

    private void updatePiecesInArea(Line line, TouchEvent event) {
        for (int i = 0; i < needChangePieces.size(); i++) {
            needChangePieces.get(i).updateWith(event, line);
        }
    }

    private void zoomPiece(PuzzlePiece piece, TouchEvent event) {
        if (piece == null || event == null || event.getPointerCount() < 2) return;
        float scale = calculateDistance(event) / previousDistance;
        piece.zoomAndTranslate(scale, scale, midPoint, getTouchX(event, 0) - downX, getTouchY(event, 0) - downY);
    }

    private void dragPiece(PuzzlePiece piece, TouchEvent event) {
        if (piece == null || event == null) return;
        piece.translate(getTouchX(event, 0) - downX, getTouchY(event, 0) - downY);
    }

    public void replace(PixelMap bitmap, String path) {
        PixelMapElement bitmapDrawable = new PixelMapElement(bitmap);
        replace(bitmapDrawable, path);
    }

    public void replace(final Element bitmapDrawable, String path) {
        if (handlingPiece == null) {
            return;
        }

        handlingPiece.setPath(path);
        handlingPiece.setDrawable(bitmapDrawable);
        handlingPiece.set(MatrixUtils.generateMatrix(handlingPiece, 0f));

        invalidate();
    }

    public void flipVertically() {
        if (handlingPiece == null) {
            return;
        }

        handlingPiece.postFlipVertically();
        handlingPiece.record();

        invalidate();
    }

    public void flipHorizontally() {
        if (handlingPiece == null) {
            return;
        }

        handlingPiece.postFlipHorizontally();
        handlingPiece.record();

        invalidate();
    }

    public void rotate(float degree) {
        if (handlingPiece == null) {
            return;
        }

        handlingPiece.postRotate(degree);
        handlingPiece.record();

        invalidate();
    }

    private PuzzlePiece findHandlingPiece() {
        for (PuzzlePiece piece : puzzlePieces) {
            if (piece.contains(downX, downY)) {
                return piece;
            }
        }
        return null;
    }

    private Line findHandlingLine() {
        for (Line line : puzzleLayout.getLines()) {
            if (line.contains(downX, downY, 40)) {
                return line;
            }
        }
        return null;
    }

    private PuzzlePiece findReplacePiece(TouchEvent event) {
        for (PuzzlePiece piece : puzzlePieces) {
            if (piece.contains(getTouchX(event, 0), getTouchY(event, 0))) {
                return piece;
            }
        }
        return null;
    }

    private List<PuzzlePiece> findNeedChangedPieces() {
        if (handlingLine == null) return new ArrayList<>();

        List<PuzzlePiece> needChanged = new ArrayList<>();

        for (PuzzlePiece piece : puzzlePieces) {
            if (piece.contains(handlingLine)) {
                needChanged.add(piece);
            }
        }

        return needChanged;
    }

    private float calculateDistance(TouchEvent event) {
        float x = getTouchX(event, 0) - getTouchX(event, 1);
        float y = getTouchY(event, 0) - getTouchY(event, 1);

        return (float) Math.sqrt(x * x + y * y);
    }

    private void calculateMidPoint(TouchEvent event, PointF point) {
        point.setPointX((getTouchX(event, 0) + getTouchX(event, 1)) / 2);
        point.setPointY((getTouchY(event, 0) + getTouchY(event, 1)) / 2);
    }

    public void reset() {
        clearPieces();
        if (puzzleLayout != null) {
            puzzleLayout.reset();
        }
    }

    public void clearPieces() {
        clearHandlingPieces();
        puzzlePieces.clear();

        invalidate();
    }

    public void clearHandlingPieces() {
        handlingLine = null;
        handlingPiece = null;
        replacePiece = null;
        needChangePieces.clear();

        invalidate();
    }

    public void addPieces(List<PixelMap> bitmaps) {
        for (PixelMap bitmap : bitmaps) {
            addPiece(bitmap);
        }
        invalidate();
    }

    public void addDrawablePieces(List<Element> drawables) {
        for (Element drawable : drawables) {
            addPiece(drawable);
        }
        invalidate();
    }

    public void addPiece(PixelMap bitmap) {
        PixelMapElement bitmapDrawable = new PixelMapElement(bitmap);
        addPiece(bitmapDrawable, null);
    }

    public void addPiece(PixelMap bitmap, Matrix initialMatrix) {
        addPiece(bitmap, initialMatrix, "");
    }

    public void addPiece(PixelMap bitmap, Matrix initialMatrix, String path) {
        PixelMapElement bitmapDrawable = new PixelMapElement(bitmap);
        addPiece(bitmapDrawable, initialMatrix, path);
    }


    public void addPiece(Element drawable) {
        addPiece(drawable, null);
    }

    public void addPiece(Element drawable, Matrix initialMatrix) {
        addPiece(drawable, initialMatrix, "");
    }

    public void addPiece(Element drawable, Matrix initialMatrix, String path) {
        int position = puzzlePieces.size();

        if (position >= puzzleLayout.getAreaCount()) {
            return;
        }

        final Area area = puzzleLayout.getArea(position);
        area.setPadding(piecePadding);

        PuzzlePiece piece = new PuzzlePiece(drawable, area, new Matrix());

        final Matrix matrix = initialMatrix != null
            ? new Matrix(initialMatrix) : MatrixUtils.generateMatrix(area, drawable, 0f);
        piece.set(matrix);

        piece.setAnimateDuration(duration);
        piece.setPath(path);

        puzzlePieces.add(piece);
        areaPieceMap.put(area, piece);

        setPiecePadding(piecePadding);
        setPieceRadian(pieceRadian);

        invalidate();
    }

    public PuzzlePiece getHandlingPiece() {
        return handlingPiece;
    }

    public int getHandlingPiecePosition() {
        if (handlingPiece == null) {
            return -1;
        }
        return puzzlePieces.indexOf(handlingPiece);
    }

    // can be null
    public boolean hasPieceSelected() {
        return handlingPiece != null;
    }

    public void setAnimateDuration(int duration) {
        this.duration = duration;
        for (PuzzlePiece piece : puzzlePieces) {
            piece.setAnimateDuration(duration);
        }
    }

    public boolean isNeedDrawLine() {
        return needDrawLine;
    }

    public void setNeedDrawLine(boolean needDrawLine) {
        this.needDrawLine = needDrawLine;
        handlingPiece = null;
        previousHandlingPiece = null;
        invalidate();
    }

    public boolean isNeedDrawOuterLine() {
        return needDrawOuterLine;
    }

    public void setNeedDrawOuterLine(boolean needDrawOuterLine) {
        this.needDrawOuterLine = needDrawOuterLine;
        invalidate();
    }

    public int getLineColor() {
        return lineColor;
    }

    public void setLineColor(int lineColor) {
        this.lineColor = lineColor;
        this.linePaint.setColor(new Color(lineColor));
        invalidate();
    }

    public int getLineSize() {
        return lineSize;
    }

    public void setLineSize(int lineSize) {
        this.lineSize = lineSize;
        invalidate();
    }

    public int getSelectedLineColor() {
        return selectedLineColor;
    }

    public void setSelectedLineColor(int selectedLineColor) {
        this.selectedLineColor = selectedLineColor;
        this.selectedAreaPaint.setColor(new Color(selectedLineColor));
        invalidate();
    }

    public int getHandleBarColor() {
        return handleBarColor;
    }

    public void setHandleBarColor(int handleBarColor) {
        this.handleBarColor = handleBarColor;
        this.handleBarPaint.setColor(new Color(handleBarColor));
        invalidate();
    }

    public boolean isTouchEnable() {
        return touchEnable;
    }

    public void setTouchEnable(boolean touchEnable) {
        this.touchEnable = touchEnable;
    }

    public void clearHandling() {
        handlingPiece = null;
        handlingLine = null;
        replacePiece = null;
        previousHandlingPiece = null;
        needChangePieces.clear();
    }

    public void setPiecePadding(float padding) {
        this.piecePadding = padding;
        if (puzzleLayout != null) {
            puzzleLayout.setPadding(padding);
            final int size = puzzlePieces.size();
            for (int i = 0; i < size; i++) {
                PuzzlePiece puzzlePiece = puzzlePieces.get(i);
                if (puzzlePiece.canFilledArea()) {
                    puzzlePiece.moveToFillArea(null);
                } else {
                    puzzlePiece.fillArea(this, true);
                }
            }
        }

        invalidate();
    }

    public void setPieceRadian(float radian) {
        this.pieceRadian = radian;
        if (puzzleLayout != null) {
            puzzleLayout.setRadian(radian);
        }

        invalidate();
    }

    public void setQuickMode(boolean quickMode) {
        this.quickMode = quickMode;
        invalidate();
    }

    public void setNeedResetPieceMatrix(boolean needResetPieceMatrix) {
        this.needResetPieceMatrix = needResetPieceMatrix;
    }

    public float getPiecePadding() {
        return piecePadding;
    }

    public float getPieceRadian() {
        return pieceRadian;
    }

    public List<PuzzlePiece> getPuzzlePieces() {
        final int size = puzzlePieces.size();
        final List<PuzzlePiece> pieces = new ArrayList<>(size);
        puzzleLayout.sortAreas();
        for (int i = 0; i < size; i++) {
            Area area = puzzleLayout.getArea(i);
            PuzzlePiece piece = areaPieceMap.get(area);
            pieces.add(piece);
        }

        return pieces;
    }

    public boolean canDrag() {
        return canDrag;
    }

    public void setCanDrag(boolean canDrag) {
        this.canDrag = canDrag;
    }

    public boolean canMoveLine() {
        return canMoveLine;
    }

    public void setCanMoveLine(boolean canMoveLine) {
        this.canMoveLine = canMoveLine;
    }

    public boolean canZoom() {
        return canZoom;
    }

    public void setCanZoom(boolean canZoom) {
        this.canZoom = canZoom;
    }

    public boolean canSwap() {
        return canSwap;
    }

    public void setCanSwap(boolean canSwap) {
        this.canSwap = canSwap;
    }

    public void setOnPieceSelectedListener(OnPieceSelectedListener onPieceSelectedListener) {
        this.onPieceSelectedListener = onPieceSelectedListener;
    }

    public interface OnPieceSelectedListener {
        void onPieceSelected(PuzzlePiece piece, int position);
    }

    public PixelMap createBitmap() throws OutOfMemoryError {
        PixelMap.InitializationOptions options = new PixelMap.InitializationOptions();
        options.size = new Size(getWidth(), getHeight());
        options.pixelFormat = PixelFormat.ARGB_8888;
        PixelMap bitmap = PixelMap.create(options);
        Texture texture = new Texture(bitmap);
        Canvas canvas = new Canvas(texture);
        Paint paint = new Paint();
        paint.setColor(Color.WHITE);
        canvas.drawRect(0, 0, getWidth(), getHeight(), paint);
        // draw pieces
        for (int i = 0; i < puzzleLayout.getAreaCount(); i++) {
            if (i >= puzzlePieces.size()) {
                break;
            }
            PuzzlePiece piece = puzzlePieces.get(i);

            if (piece == handlingPiece && currentMode == ActionMode.SWAP) {
                continue;
            }

            if (puzzlePieces.size() > i) {
                piece.draw(canvas, quickMode);
            }
        }
        return bitmap;
    }
}
